/******************************************************************************\
    Post.cpp contains functions for http requests.
    It's used with acm server plugins. See DllMain.cpp for details.
    Copyright (C) 2008  Victor Vinogradov (fly3333@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
\******************************************************************************/

#include <winsock2.h>
#include ".\Post.h"
#include <Common\Socket.hpp>
#include <Common\SocketStream.hpp>
#include <Common\String.h>
#include <Common\Log.h>
#include <map>
#include <string>
#include <vector>
using namespace std;

extern cLog Log;

cCriticalData< unsigned short > Port = 0;
static const char Host[] = "localhost";
static map< string, string > Cookie;
static cSocket Socket;
static cCriticalSection PostCriticalSection;

static bool __Post( const string &request, string &response, string &location )
{
	static const size_t MAX_BUF = 65535;
	cSocketStream socket_stream;

	if( !socket_stream.Create( Socket, MAX_BUF ) )
	{
		Log.Error( "__Post::socket_stream.Create returned false.\n" );
		return false;
	}
	if( !socket_stream->PutLine( request ) )
	{
		if( !Socket.Create( AF_INET, SOCK_STREAM, 0 ) )
		{
			Log.Error( "__Post::Socket.Create returned false.\n" );
			return false;
		}
		HOSTENT *host = gethostbyname( Host );
		if( host == NULL )
		{
			Log.Error( "__Post::gethostbyname returned NULL.\n" );
			return false;
		}
		if( Socket.Connect( ( sockaddr * )&sSockAddrIn( AF_INET, htons( Port ), sInAddr( *( unsigned long * )host->h_addr ) ), sizeof( sockaddr_in ) ) == SOCKET_ERROR )
		{
			Log.Error( "__Post::Socket.Connect returned SOCKET_ERROR.\n" );
			return false;
		}
		if( !socket_stream.Create( Socket, MAX_BUF ) )
		{
			Log.Error( "__Post::socket_stream.Create returned false.\n" );
			return false;
		}
		if( !socket_stream->PutLine( request ) )
		{
			Log.Error( "__Post::socket_stream.PutLine returned false.\n" );
			return false;
		}
	}

	response.clear();
	location.clear();

	bool is_connection_close = false;
	int content_length = 0;

	string line;
	while( ( line = socket_stream->GetLine() ) != "\r\n" )
	{
		if( line == "" )
		{
			Log.Error( "__Post::socket_stream.GetLinec returned \"\".\n" );
			return false;
		}
		vector< string > data = Split( line, ":" );
		if( data.size() >= 2 )
		{
			string name = ToLower( Trim( data.front() ) );
			data.erase( data.begin() );
			string value = Trim( Concat( data, ":" ) );
			if( name == "content-length" )
			{
				if( sscanf( value.c_str(), "%d", &content_length ) != 1 )
				{
					Log.Error( "__Post::sscanf(content_length) didn't return 1.\n" );
					return false;
				}
			}
			else if( name == "proxy-connection" )
			{
				if( ToLower( value ) == "close" )
					is_connection_close = true;
			}
			else if( name == "set-cookie" )
			{
				vector< string > temp1 = Split( value, ";" );
				for( vector< string >::const_iterator temp1_iterator = temp1.begin(); temp1_iterator != temp1.end(); ++temp1_iterator )
				{
					vector< string > temp2 = Split( Trim( *temp1_iterator ), "=" );
					string name = Trim( temp2.front() );
					if( name != "path" &&
						 name != "expires" )
					{
						temp2.erase( temp2.begin() );
						Cookie[ name ] = Trim( Concat( temp2, "=" ) );
					}
				}
			}
			else if( name == "location" )
			{
				location = value;
			}
		}
	}

	response = socket_stream->Get( content_length );
	if( ( int )response.length() != content_length )
	{
		Log.Error( "__Post: Received data is less than declared.\n" );
		return false;
	}

	if( is_connection_close )
		Socket.Close();
	return true;
}

static string MakeCookie()
{
	string result;
	if( Cookie.size() != 0 )
	{
		result += "Cookie: ";
		for( map< string, string >::const_iterator cookie_iterator = Cookie.begin(); cookie_iterator != Cookie.end(); ++cookie_iterator )
		{
			if( cookie_iterator != Cookie.begin() )
				result += "; ";
			result += StringPrintf( "%s=%s", cookie_iterator->first.c_str(), cookie_iterator->second.c_str() );
		}
		result += "\r\n";
	}
	return result;
}

bool HttpGet( const string &host, const string &site, string &response, const char *referer )
{
	map< string, string > header;
	if( referer != NULL )
		header[ "Referer" ] = referer;
	return HttpGet( host, site, response, header );
}

bool HttpGet( const string &host, const string &site, string &response, const map< string, string > &header )
{
	cLocker locker( PostCriticalSection );
	string request, location;
	request += StringPrintf( "GET %s HTTP/1.1\r\n", site.c_str() );
	request += "Accept: */*\r\n";
	for( map< string, string >::const_iterator it = header.begin(), end = header.end(); it != end; ++it )
		request += StringPrintf( "%s: %s\r\n", it->first.c_str(), it->second.c_str() );
	request += StringPrintf( "Host: %s\r\n", host.c_str() );
	request += "Proxy-Connection: Keep-Alive\r\n";
	request += "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)\r\n";
	request += MakeCookie();
	request += "\r\n";
	return __Post( request, response, location );
}

bool HttpPost( const string &host, const string &site, const string &data, string &response, string &location, const char *referer )
{
	map< string, string > header;
	if( referer != NULL )
		header[ "Referer" ] = referer;
	return HttpPost( host, site, data, response, location, header );
}

bool HttpPost( const string &host, const string &site, const string &data, string &response, string &location, const map< string, string > &header )
{
	cLocker locker( PostCriticalSection );
	string request;
	request += StringPrintf( "POST %s HTTP/1.1\r\n", site.c_str() );
	request += StringPrintf( "Host: %s\r\n", host.c_str() );
	request += "Proxy-Connection: Keep-Alive\r\n";
	request += "Content-Type: application/x-www-form-urlencoded\r\n";
	request += "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)\r\n";
	request += "Accept: */*\r\n";
	for( map< string, string >::const_iterator it = header.begin(), end = header.end(); it != end; ++it )
		request += StringPrintf( "%s: %s\r\n", it->first.c_str(), it->second.c_str() );
	request += StringPrintf( "Content-Length: %lu\r\n", data.length() );
	request += MakeCookie();
	request += "\r\n";
	request += data;
	return __Post( request, response, location );
}

bool HttpMultiPost( const std::string &host, const std::string &site, const std::string &boundary, const std::string &data, std::string &response, std::string &location, const char *referer )
{
	cLocker locker( PostCriticalSection );
	string request;
	request += StringPrintf( "POST %s HTTP/1.1\r\n", site.c_str() );
	request += StringPrintf( "Host: %s\r\n", host.c_str() );
	request += "Proxy-Connection: Keep-Alive\r\n";
	request += StringPrintf( "Content-Type: multipart/form-data; boundary=%s\r\n", boundary.c_str() );
	request += "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)\r\n";
	request += "Accept: */*\r\n";
	if( referer != NULL )
		request += StringPrintf( "Referer: %s\r\n", referer );
	request += StringPrintf( "Content-Length: %lu\r\n", data.length() );
	request += MakeCookie();
	request += "\r\n";
	request += data;
	return __Post( request, response, location );
}
